/*
 * @function: 用于存储从网络下载下来的数据，可以被LRU清除，生命周期本类会自己管理
 */
package me.weijing.common.http;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import me.weijing.common.util.FileUtil;
import me.weijing.common.util.MD5Util;
import android.content.Context;
import android.os.Environment;
import android.os.StatFs;
import android.text.TextUtils;

/**
 * 
 * 存储文件
 * 
 * @author 2013-8-19上午8:53:07
 * @version 1.0.0
 * @category 杭州尤拉夫科技有限公司(微警)
 */
public class FileCacheStore {

    private static String sdcarRootPath = null;
    private static String dataRootPath = Environment.getDataDirectory().getAbsolutePath();
    private static String sdcarTempRootPath;
    private static String dataTempRootPath;
    public static final long DEFAULT_DATACACHE_CAPACITY = 1024 * 1024 * 256L; // 256MB
    public static final long DEFAULT_SDCARDCACHE_CAPACITY = 2147483648L; // 2GB
    private static long dataCacheCapacity = DEFAULT_DATACACHE_CAPACITY;
    private static long sdCacheCapacity = DEFAULT_SDCARDCACHE_CAPACITY; // 2GB
    private static boolean bInited = false;
    private static long sdcarUsage = 0;
    private static long dataStorageUsage = 0;
    private static int maxSubDirs = 16;
    private static Thread cleanThread = null;
    private static FileCacheStore cacheInstance;

    public static String JEPG = ".jpg";
    public static String AMR = ".amr";
    public static String MP4 = ".mp4";

    public static FileCacheStore getInstance() {
        if (null == cacheInstance) {
            cacheInstance = new FileCacheStore();
        }
        return cacheInstance;
    }

    public static long getSdcarUsage() {
        return sdcarUsage;
    }

    public static void setSdcarUsage(long sdcarUsage) {
        FileCacheStore.sdcarUsage = sdcarUsage;
    }

    public static long getDataStorageUsage() {
        return dataStorageUsage;
    }

    public static void setDataStorageUsage(long dataStorageUsage) {
        FileCacheStore.dataStorageUsage = dataStorageUsage;
    }

    public static void Init(Context context, long inerDataMaxCapacity, long sdcardMaxCapacity) {
        if (bInited)
            return;
        // dataCacheCapacity

        String pkgName = context.getPackageName();
        sdcarRootPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + pkgName + "/cache/";
        sdcarTempRootPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + pkgName + "/.temp/";
        dataRootPath = context.getCacheDir().getAbsolutePath() + "/" + pkgName + "/";
        dataTempRootPath = context.getCacheDir().getAbsolutePath() + "/" + pkgName + ".temp/";
        File tf = null;
        tf = new File(dataTempRootPath);
        if (!tf.exists()) {
            tf.mkdirs();
        }

        File fp = new File(dataRootPath);
        if (!fp.exists()) {
            fp.mkdirs();
        }
        for (int i = 0; i < maxSubDirs; i++) {
            String subdir = dataRootPath + String.valueOf(i);
            fp = new File(subdir);
            if (!fp.exists())
                fp.mkdirs();
        }

        if (isSDCardAvailable()) {
            tf = new File(sdcarTempRootPath);
            if (!tf.exists()) {
                tf.mkdirs();
            }
            fp = new File(sdcarRootPath);
            if (!fp.exists()) {
                fp.mkdirs();
            }
            for (int i = 0; i < maxSubDirs; i++) {
                String subdir = sdcarRootPath + String.valueOf(i);
                fp = new File(subdir);
                if (!fp.exists())
                    fp.mkdirs();
            }

        }

        StatFs stfs = new StatFs(dataRootPath);
        long freeSpace = (long) stfs.getBlockSize() * (long) stfs.getAvailableBlocks();
        if (freeSpace > 1024 * 1024 * 40) {
            freeSpace -= 1024 * 1024 * 10;
        }
        if (inerDataMaxCapacity <= DEFAULT_DATACACHE_CAPACITY)
            inerDataMaxCapacity = DEFAULT_DATACACHE_CAPACITY;
        dataCacheCapacity = Math.min(inerDataMaxCapacity, freeSpace);

        if (isSDCardAvailable()) {

            StatFs sdstfs = new StatFs(Environment.getExternalStorageDirectory().getAbsolutePath());

            freeSpace = (long) sdstfs.getBlockSize() * (long) sdstfs.getAvailableBlocks();
            if (freeSpace > 1024 * 1024 * 40) {
                freeSpace -= 1024 * 1024 * 10;
            }
            if (sdcardMaxCapacity <= DEFAULT_SDCARDCACHE_CAPACITY)
                sdcardMaxCapacity = DEFAULT_SDCARDCACHE_CAPACITY;
            sdCacheCapacity = Math.min(sdcardMaxCapacity, freeSpace);
        }
        cleanThread = new Thread(new FileCacheStore.MyCleanDiskThread());
        cleanThread.start();
        bInited = true;
    }

    public static void UnInit(Context context) {
        bInited = false;
        cleanThread.interrupt();
        cleanThread = null;
    }

    public static boolean isSDCardAvailable() {
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
            return true;
        else
            return false;
    }

    public static boolean isSDCardFull() {
        StatFs fs = new StatFs(Environment.getExternalStorageDirectory().getAbsolutePath());
        return !(fs.getAvailableBlocks() > 1);
    }

    public static boolean isInnerStorageFull() {
        StatFs fs = new StatFs(dataRootPath);
        return !(fs.getAvailableBlocks() > 1);
    }

    public static String genNewCacheFile() {
        return genTempCacheFile();
        /*
         * java.util.UUID a= java.util.UUID.randomUUID(); String
         * uuidstr=a.toString(); String md5url=MD5Util.md5(uuidstr);
         * md5url=String
         * .valueOf(Math.abs(md5url.hashCode())%maxSubDirs)+File.separator
         * +md5url; String realfile=null;
         * 
         * if(isSDCardAvailable() && isSDCardFull()==false){
         * realfile=sdcarRootPath+md5url; }else{ realfile=dataRootPath+md5url; }
         * return realfile;
         */
    }

    public static String genTempCacheFile() {
        java.util.UUID a = java.util.UUID.randomUUID();
        String uuidstr = a.toString();
        String md5url = MD5Util.md5(uuidstr);
        String realfile = null;
        if (isSDCardAvailable() && isSDCardFull() == false) {
            realfile = sdcarTempRootPath + md5url;
        } else {
            realfile = dataTempRootPath + md5url;
        }
        new File(realfile).getParentFile().mkdirs();
        return realfile;
    }

    public static String genCacheFilePath(String url) {
        String md5url = MD5Util.md5(url);
        int t1 = Math.abs(Math.abs(md5url.hashCode()) % maxSubDirs);
        md5url = String.valueOf(t1) + File.separator + md5url;
        String realfile;
        if (isSDCardAvailable() && isSDCardFull() == false) {
            realfile = sdcarRootPath + md5url;
        } else {
            realfile = dataRootPath + md5url;
        }
        return realfile;
    }

    public static String migrateFile(String localPath, String toUrl) {

        File f = new File(localPath);
        if (f.exists() == false)
            return "";
        long fileLen = f.length();
        String md5url = MD5Util.md5(toUrl);
        int t1 = Math.abs(Math.abs(md5url.hashCode()) % maxSubDirs);
        md5url = String.valueOf(t1) + File.separator + md5url;

        String realfile = null;

        if (isSDCardAvailable() && isSDCardFull() == false) {
            realfile = sdcarRootPath + md5url;
            // boolean bret=f.renameTo(new File(realfile));
            boolean bret = FileUtil.renameFile(localPath, realfile);
            if (bret) {
                sdcarUsage += fileLen;
                return realfile;
            }
            realfile = dataRootPath + md5url;
        } else {
            realfile = dataRootPath + md5url;
        }
        new File(realfile).getParentFile().mkdirs();
        // boolean bret=f.renameTo(new File(realfile));
        boolean bret = FileUtil.renameFile(localPath, realfile);
        if (bret) {
            dataStorageUsage += fileLen;
            return realfile;
        } else
            return "";
    }

    public static String saveCacheFile(String url, byte[] data) {
        if (null == url || null == data)
            return null;
        String md5url = MD5Util.md5(url);
        int t1 = Math.abs(Math.abs(md5url.hashCode()) % maxSubDirs);

        md5url = String.valueOf(t1) + File.separator + md5url;
        String realfile = null;
        boolean bSDCard = false;
        if (isSDCardAvailable()) {
            realfile = sdcarRootPath + md5url;
            bSDCard = true;
            if (data.length + sdcarUsage > sdCacheCapacity) {
                bSDCard = false;
                realfile = dataRootPath + md5url;
                if (data.length + dataStorageUsage > dataCacheCapacity) {
                    return null;
                }
            }
        } else {
            if (data.length + dataStorageUsage > dataCacheCapacity) {
                return null;
            }
            realfile = dataRootPath + md5url;
        }
        new File(realfile).getParentFile().mkdirs();
        String realfileTemp = realfile + ".tmp";
        File file = new File(realfileTemp);
        if (file.exists()) {
            long oldsize = 0;
            try {
                oldsize = FileUtil.getFileSize(file);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (bSDCard) {
                sdcarUsage -= oldsize;
            } else {
                dataStorageUsage -= oldsize;
            }
            file.delete();
        }
        FileOutputStream fos = null;
        boolean bSuccess = false;
        try {
            fos = new FileOutputStream(file);
            fos.write(data);
            fos.flush();
            bSuccess = true;
            if (bSDCard) {
                sdcarUsage += data.length;
            } else {
                dataStorageUsage += data.length;
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {

                    e.printStackTrace();
                }
            }
        }
        if (bSuccess) {
            // boolean bret=file.renameTo(new File(realfile));
            boolean bret = FileUtil.renameFile(realfileTemp, realfile);
            if (bret)
                return realfile;
            else
                return null;
        } else {
            file.delete();
            return null;
        }
    }

    public static String getCacheFilePath(String url) {
        if (url.startsWith("http") == false)
            return url;
        String md5url = MD5Util.md5(url);
        md5url = String.valueOf(Math.abs(md5url.hashCode() % maxSubDirs)) + File.separator + md5url;
        String realfile = null;

        if (isSDCardAvailable()) {
            realfile = sdcarRootPath + md5url;
            File f = new File(realfile);
            if (f.exists() == false) {
                realfile = dataRootPath + md5url;

            }
        } else {
            realfile = dataRootPath + md5url;

        }
        new File(realfile).getParentFile().mkdirs();
        return realfile;

    }

    public static String getCacheFileTempPath(String url) {
        String realFile = genCacheFilePath(url);
        realFile += ".tmp";
        new File(realFile).getParentFile().mkdirs();
        return realFile;
    }

    public static String getCacheFileMetaPath(String url) {
        String realFile = genCacheFilePath(url);
        realFile += ".meta";
        new File(realFile).getParentFile().mkdirs();
        return realFile;
    }

    public static FileInputStream openCacheFile(String url) throws Exception {

        String realFile = getCacheFilePath(url);
        if (null == realFile)
            return null;
        return FileUtil.openFile(realFile);

    }

    public static byte[] getCacheFileData(String url) throws Exception {
        InputStream fis = openCacheFile(url);
        if (null == fis) {
            throw new FileNotFoundException();
        }
        int len = fis.available();
        byte[] retBytes = new byte[len];
        fis.read(retBytes);
        fis.close();
        return retBytes;
    }

    public static void deleteCacheFile(String url) {
        String md5url = MD5Util.md5(url);
        md5url = String.valueOf(Math.abs(md5url.hashCode() % maxSubDirs)) + File.separator + md5url;
        String realfile = null;
        if (isSDCardAvailable()) {
            realfile = sdcarRootPath + md5url;
            long flen = FileUtil.deleteFile(realfile);
            sdcarUsage -= flen;
            realfile = dataRootPath + md5url;
            flen = FileUtil.deleteFile(realfile);
            dataStorageUsage -= flen;
        } else {
            realfile = dataRootPath + md5url;
            long flen = FileUtil.deleteFile(realfile);
            dataStorageUsage -= flen;
        }
    }

    public static void cleanCache() {
        cleanTempDir();
        if (isSDCardAvailable()) {
            FileUtil.removeDir(sdcarRootPath);
            String usageMetaFile = sdcarRootPath + "/.usagemeta";
            new File(usageMetaFile).getParentFile().mkdirs();
            sdcarUsage = 0;
            FileUtil.writeFile(usageMetaFile, String.valueOf(sdcarUsage));
        }
        FileUtil.removeDir(dataRootPath);
        String usageMetaFile = dataRootPath + "/.usagemeta";
        new File(usageMetaFile).getParentFile().mkdirs();
        dataStorageUsage = 0;
        FileUtil.writeFile(usageMetaFile, String.valueOf(dataStorageUsage));
    }

    // ////////// following is private part ///////////
    public static void cleanTempDir() {
        if (isSDCardAvailable()) {
            cleanTempDir(sdcarTempRootPath);
        }

        cleanTempDir(dataTempRootPath);
    }

    static void cleanTempDir(String rootDir) {
        File f = new File(rootDir);
        File flist[] = f.listFiles();
        if (null == flist)
            return;
        long nowt = System.currentTimeMillis();
        for (int i = 0; i < flist.length; i++) {
            File tf = flist[i];
            if (nowt - tf.lastModified() < 30000)
                continue;
            tf.delete();
        }
    }

    public static class MyCleanDiskThread implements Runnable {
        long lastSdcarUsage = 0;
        long lastDataStorageUsage = 0;

        @Override
        public void run() {
            cleanTempDir();
            if (isSDCardAvailable()) {
                // sdcarUsage += FileUtil.getFileSize(sdcarRootPath);
                String usageMetaFile = sdcarRootPath + "/.usagemeta";
                long tmpUsage = loadUsageMeta(usageMetaFile);
                if (0 == tmpUsage) {
                    sdcarUsage += FileUtil.getFileSize(sdcarRootPath);
                    FileUtil.writeFile(usageMetaFile, String.valueOf(sdcarUsage));
                } else {
                    sdcarUsage += tmpUsage;
                }
            }
            // dataStorageUsage += FileUtil.getFileSize(dataRootPath);
            String usageMetaFile = dataRootPath + "/.usagemeta";
            long tmpUsage = loadUsageMeta(usageMetaFile);
            if (0 == tmpUsage) {
                dataStorageUsage += FileUtil.getFileSize(dataRootPath);
                FileUtil.writeFile(usageMetaFile, String.valueOf(dataStorageUsage));
            } else {
                dataStorageUsage += tmpUsage;
            }

            lastSdcarUsage = sdcarUsage;
            lastDataStorageUsage = dataStorageUsage;

            while (bInited) {
                try {
                    Thread.sleep(30000);// 线程暂停10秒，单位毫秒
                    checkAndCleanDisk();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (Throwable t) {
                    t.printStackTrace();
                }

            }
        }

        long loadUsageMeta(String metaFilePath) {
            String data = FileUtil.readFileContent(metaFilePath);
            if (TextUtils.isEmpty(data))
                return 0;
            return Long.valueOf(data);
        }

        void checkAndCleanDisk() {

            if (lastSdcarUsage != sdcarUsage) {

                // store usage to file
                String usageMetaFile = sdcarRootPath + "/.usagemeta";

                FileUtil.writeFile(usageMetaFile, String.valueOf(lastSdcarUsage));
                lastSdcarUsage = sdcarUsage;
            }
            if (lastDataStorageUsage != dataStorageUsage) {

                String usageMetaFile = dataRootPath + "/.usagemeta";
                FileUtil.writeFile(usageMetaFile, String.valueOf(lastDataStorageUsage));
                lastDataStorageUsage = dataStorageUsage;
            }
            if (FileCacheStore.getDataStorageUsage() >= FileCacheStore.dataCacheCapacity) {
                try {
                    cleanupDiskDataCache();
                } catch (Throwable t) {
                    t.printStackTrace();
                }

            }
            if (FileCacheStore.getSdcarUsage() >= FileCacheStore.sdCacheCapacity) {
                try {
                    cleanupDiskSdCardCache();
                } catch (Throwable t) {
                    t.printStackTrace();
                }
            }
        }

        private int cleanupDiskCache(String dirPath) {
            long nowSeconds = System.currentTimeMillis() / 1000;
            int totalDeledSize = 0;
            File rootDir = new File(dirPath);
            File[] subDirs = rootDir.listFiles();
            if (null == subDirs)
                return totalDeledSize;
            for (int i = 0; i < subDirs.length; i++) {
                File subdir = subDirs[i];
                if (!subdir.isDirectory())
                    continue;
                // TODO, 是否需要处理.,..
                File[] subfiles = FileUtil.listFilesSortByModify(subdir.getAbsolutePath());
                if (subfiles == null) {
                    continue;
                }
                // 每个子目录最多删除4MB
                int deledFileSize = 0;
                int delMaxSize = 1024 * 1024 * 4;// 4MB
                for (int j = 0; j < subfiles.length; j++) {
                    long fileSeconds = subfiles[j].lastModified() / 1000;
                    if (nowSeconds - fileSeconds < 3600)
                        continue;
                    deledFileSize += subfiles[j].length();
                    subfiles[j].delete();
                    if (deledFileSize > delMaxSize)
                        break;
                }
                totalDeledSize += deledFileSize;
            }
            return totalDeledSize;
        }

        private void cleanupDiskDataCache() {
            int deledFileSize = cleanupDiskCache(dataRootPath);
            dataStorageUsage -= deledFileSize;
        }

        private void cleanupDiskSdCardCache() {
            if (!isSDCardAvailable()) {
                return;
            }
            int deledFileSize = cleanupDiskCache(sdcarRootPath);
            sdcarUsage -= deledFileSize;
        }
    }

}
